﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace ExpressionTreeLib.ViewModels
{
    [ExpressionType(typeof(MethodCallExpression))]
    public class MethodCallViewModel : NodeViewModel
    {
        private Type _declareType;

        [TypeConverter(typeof(TypeTypeConverter))]
        public Type DeclareType
        {
            get { return _declareType; }
            set
            {
                if (value != this.DeclareType)
                    this.MethodName = new Method {MethodInfo = value.GetMethods().First()};

                _declareType = value;
                this.NotifyOfPropertyChange(()=>DeclareType);
                this.ReloadDetailView();
            }
        }

        private Method _methodName;
        public Method MethodName
        {
            get { return _methodName; }
            set
            {
                if( value != null && this.MethodName != null && value.MethodInfo.Equals(this.MethodName.MethodInfo))
                    return;

                var originalMethod = _methodName;
                _methodName = value;
                this.NotifyOfPropertyChange(()=>MethodName);

                this.Children.Clear();

                this.Object = string.Empty;
                this.Children.Add(new EmptyViewModel{ Name = "???", Parent = this});

                foreach (var parameterInfo in value.MethodInfo.GetParameters())
                {
                    this.Children.Add(new EmptyViewModel{ Name = "???", Parent = this });
                }
            }
        }

        public string Object { get; private set; }

        public override void LoadExpressionNode(Expression expression)
        {
            base.LoadExpressionNode(expression);
            
            var methodCallExpression = expression as MethodCallExpression;
            if (methodCallExpression == null)
                return;

            this.Object = methodCallExpression.Object == null ? string.Empty : methodCallExpression.Object.ToString();
            this._declareType = methodCallExpression.Method.DeclaringType;
            this._methodName = new Method { MethodInfo = methodCallExpression.Method };

            ExpressionNodeTypeMapping.Create(methodCallExpression.Object, this);
            foreach (var argument in methodCallExpression.Arguments)
            {
                ExpressionNodeTypeMapping.Create(argument, this);
            }
        }

        protected override Expression CreateExpression()
        {
            var instance = this.Children[0];

            var arguments = this.Children.Skip(1).Select(argumentVM => argumentVM.Expression).ToList();

            return Expression.Call(instance.Expression, this.MethodName.MethodInfo, arguments);
        }

        public override void GenerateCode(StringBuilder stringBuilder)
        {
            stringBuilder.Append("Expression.Call(");
            IndentStringBuilder.Current.IncreaseIndent(stringBuilder);

            this.Children[0].GenerateCode(stringBuilder);

            stringBuilder.AppendFormat(", typeof({0}).GetMethod(\"{1}\", new Type[]{{", this.DeclareType.Name, this.MethodName.MethodInfo.Name);

            var parameters = this.MethodName.MethodInfo.GetParameters();
            for (int i = 0; i < parameters.Length; i++)
            {
                stringBuilder.AppendFormat("typeof({0})", parameters[i].ParameterType.Name);
                if (i < parameters.Length - 1)
                    stringBuilder.Append(", ");
            }

            stringBuilder.Append("}), ");

            IndentStringBuilder.Current.IncreaseIndent(stringBuilder);
            foreach (var argumentVM in this.Children.Skip(1))
            {
                argumentVM.GenerateCode(stringBuilder);
                if( argumentVM == this.Children.Last())
                    break;

                stringBuilder.Append(",");
                IndentStringBuilder.Current.StartNewLine(stringBuilder);
            }
            IndentStringBuilder.Current.DecreaseIndent();

            stringBuilder.Append(")");
            IndentStringBuilder.Current.DecreaseIndent();
        }
    }
}
